package com.smh.hmdianping.util;

import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * @author shiminghui
 * @date 2025/3/27 21:34
 * @description: TODO
 */
@Component
@Slf4j
public class RedisTemplateUtil {

    private static final ObjectMapper objectMapper = new ObjectMapper();
    private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            0,
            8,
            10,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(10),
            new ThreadPoolExecutor.DiscardPolicy()
    );
    @Autowired
    private StringRedisTemplate redisTemplate;

    public StringRedisTemplate getRedisTemplate() {
        return redisTemplate;
    }


    // 设置缓存,并设置过期时间
    public void set(String key, Object value, Long expire, TimeUnit unit) {

        // 将值序列化为json字符串
        String jsonString;
        if (value instanceof String) {
            jsonString = (String) value;
        } else {
            try {
                jsonString = objectMapper.writeValueAsString(value);
            } catch (JsonProcessingException e) {
                log.error("序列化json失败", e);
                throw new RuntimeException(e);
            }
        }

        // 设置key-value
        redisTemplate.opsForValue().set(key, jsonString, expire, unit);

    }

    // 设置逻辑过期时间, 解决缓存击穿问题
    public void setWithLogicalExpire(String key, Object value, Long expire, TimeUnit unit) {

        // 创建包含逻辑过期时间的对象
        LogicalExpire logicalExpire = new LogicalExpire();
        logicalExpire.setData(value);
        logicalExpire.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(expire)));

        // 将值序列化为json字符串
        String jsonString = null;
        jsonString = JSONUtil.toJsonStr(logicalExpire);
        // 设置key-value
        redisTemplate.opsForValue().set(key, jsonString);
    }


    // 获取缓存,并解决缓存穿透问题
    public <R, ID> R get(String prefix, ID id, Class<R> clazz, Long expire, TimeUnit unit, Function<ID, R> dbFallback) {

        // 拼接key
        String key = prefix + id;
        // 查询缓存
        String s = redisTemplate.opsForValue().get(key);
        if (!StringUtils.hasLength(s)) {
            // 缓存穿透
            // 向数据库查询
            R r = dbFallback.apply(id);
            if (r == null) {
                // 将空值写入redis
                this.set(key, "null", expire, TimeUnit.MINUTES);
                // 返回错误信息
                return null;
            }
            this.set(key, r, expire, unit);
            return r;
        }
        if (s.equals("null")) {
            // 直接返回
            return null;
        }
        // 将json反序列化为对象
        if (clazz == String.class) {
            return (R) s;
        }
        try {
            R r = objectMapper.readValue(s, clazz);
            return r;
        } catch (JsonProcessingException e) {
            log.error("反序列化json失败", e);
            throw new RuntimeException(e);
        }
    }

    // 获取缓存,并解决缓存击穿问题,使用逻辑过期解决
    public <R, ID> R getWithLogicalExpire(String prefix, ID id, Class<R> clazz, Long expire, TimeUnit unit, Function<ID, R> dbFallback, Long lockExpire, TimeUnit lockUnit) {
        // 拼接key
        String key = prefix + id;
        // 查询缓存
        String s = redisTemplate.opsForValue().get(key);
        // 不考虑查不到的情况,一般会进行缓存预热
        if (!StringUtils.hasLength(s)) {
            // 查询数据库
            R r = dbFallback.apply(id);
            if (r == null) {
                // 将空值写入redis
                this.setWithLogicalExpire(key, "null", expire, TimeUnit.MINUTES);
                // 返回错误信息
                return null;
            }
            this.setWithLogicalExpire(key, r, expire, unit);
            return r;
        }
        if (s.equals("null")) {
            return null;
        }
        // 将json反序列化为对象
        LogicalExpire logicalExpire = JSONUtil.toBean(s, LogicalExpire.class);
        R r = (R) logicalExpire.getData();
        LocalDateTime expireTime = logicalExpire.getExpireTime();
        // 判断是否过期
        if (expireTime.isAfter(LocalDateTime.now())) {
            // 没有过期, 直接返回
            return r;
        }
        // 过期, 查询数据库
        // 获取锁
        String lockKey = prefix + "lock";
        if (tryLock(lockKey, lockExpire, lockUnit)) {
            // 获取锁成功, 开启新线程去查询数据库
            threadPoolExecutor.submit(() -> {
                // 从重建缓存
                // 查询数据库
                R newR = dbFallback.apply(id);
                // 将查询到的数据写入redis
                setWithLogicalExpire(key, newR, expire, unit);
                // 释放锁
                unLock(lockKey);
            });
        }
        // 获取锁失败, 从缓存中取数据
        return r;
    }

    // 通过redis获取一把互斥锁
    public boolean tryLock(String key, Long expire, TimeUnit unit) {
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "", expire, unit);
        return Boolean.TRUE.equals(flag);
    }

    // 释放锁
    public void unLock(String key) {
        redisTemplate.delete(key);
    }

    @Data
    private class LogicalExpire {
        private Object data;
        private LocalDateTime expireTime;
    }

}
